package analysis

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"github.com/goburrow/modbus"
	"modbus/util"
	"strconv"
	"time"
)

func (dev *DevInfoTable) Connect() error {
	if dev.DevInfo.InterType == "ETHERNET" {
		handler := modbus.NewTCPClientHandler(dev.DevInfo.Address + ":" + dev.DevInfo.Port)
		// Connect manually so that multiple requests are handled in one session
		err := handler.Connect()
		if err != nil {
			fmt.Println(err)
			return err
		}
		handler.Timeout = time.Duration(dev.DevInfo.DevTimeout) * time.Second
		dev.DevInfo.HandleClient = handler
	} else if dev.DevInfo.InterType == "RS485" {
		handler := modbus.NewRTUClientHandler(dev.DevInfo.InterName)
		handler.BaudRate = dev.DevInfo.BaudRate
		handler.DataBits = dev.DevInfo.DataBit
		switch dev.DevInfo.ParityBit {
		case 0: //NONE
			handler.Parity = "N"
		case 1: //EVEN
			handler.Parity = "E"
		case 2: //ODD
			handler.Parity = "O"
		}
		handler.StopBits = dev.DevInfo.StopBit
		handler.Timeout = time.Duration(dev.DevInfo.DevTimeout) * time.Second
		dev.DevInfo.HandleRtuClient = handler
		// Connect manually so that multiple requests are handled in one session
		err := handler.Connect()
		if err != nil {
			fmt.Println(err)
			return err
		}
	}

	return nil
}

const (
	INTERVAL_TIME = 5000
)

func (dev *DevInfoTable) Start() {
	var client modbus.Client
	defer func() {
		if dev.DevInfo.InterType == "ETHERNET" {
			dev.DevInfo.HandleClient.Close()
		} else if dev.DevInfo.InterType == "RS485" {
			dev.DevInfo.HandleRtuClient.Close()
		}
	}()
	if dev.DevInfo.InterType == "ETHERNET" {
		client = modbus.NewClient(dev.DevInfo.HandleClient)
		dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
	} else if dev.DevInfo.InterType == "RS485" {
		client = modbus.NewClient(dev.DevInfo.HandleRtuClient)
		dev.DevInfo.HandleRtuClient.SlaveId = byte(dev.DevInfo.DevId)
	}
	dev.DevInfo.ModbusClient = client

	dev.ModbusRegRead()
	second_count := 0
	for {
		time.Sleep(time.Microsecond * 1000)
		second_count++
		if second_count >= INTERVAL_TIME {
			dev.Interval_Collect_Report()
			second_count = 0
		} else {
			Collect_Show(dev)
		}
	}
}

func (dev *DevInfoTable) CoilRegRead(table Collect_table) {
	for {
		time.Sleep(time.Millisecond * time.Duration(table.Period))
		//dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
		if dev.DevInfo.InterType == "ETHERNET" {
			dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
		} else if dev.DevInfo.InterType == "RS485" {
			dev.DevInfo.HandleRtuClient.SlaveId = byte(dev.DevInfo.DevId)
		}
		//results, _ := client.ReadCoils(uint16(table.StartAddr), uint16(table.Length))
		results, _ := dev.DevInfo.ModbusClient.ReadCoils(uint16(table.StartAddr), uint16(table.Length))
		for i, result := range results {
			for j := 0; j < 8; j++ {
				if result>>j&1 == 1 {
					dev.RegBufTable.Coils[table.StartAddr+i*8+j] = 1
					//fmt.Println(time.Now(), table.StartAddr+i*8+j, true)
				} else {
					//fmt.Println(time.Now(), table.StartAddr+i*8+j, false)
					dev.RegBufTable.Coils[table.StartAddr+i*8+j] = 0
				}
				if dev.RegBufTable.Coils[table.StartAddr+i*8+j] != dev.RegBufStateTable.Coils[table.StartAddr+i*8+j] {
					dev.RegBufStateTable.Coils[table.StartAddr+i*8+j] = dev.RegBufTable.Coils[table.StartAddr+i*8+j]
					dev.RegBufStateTable.LockData.Lock()
					dev.RegBufStateTable.CoilsState[table.StartAddr+i*8+j] = 1
					dev.RegBufStateTable.Timestamp = time.Now()
					dev.RegBufStateTable.LockData.Unlock()
				}
			}
		}

	}
}

func (dev *DevInfoTable) DiscreteInputRegRead(table Collect_table) {
	for {
		time.Sleep(time.Millisecond * time.Duration(table.Period))
		//dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
		if dev.DevInfo.InterType == "ETHERNET" {
			dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
		} else if dev.DevInfo.InterType == "RS485" {
			dev.DevInfo.HandleRtuClient.SlaveId = byte(dev.DevInfo.DevId)
		}
		results, err := dev.DevInfo.ModbusClient.ReadDiscreteInputs(uint16(table.StartAddr), uint16(table.Length))
		if err != nil {
			fmt.Println(err, table.StartAddr, table.Length)
		}
		for i, result := range results {
			for j := 0; j < 8; j++ {
				if result>>j&1 == 1 {
					dev.RegBufTable.DiscreteInputs[table.StartAddr+i*8+j] = 1
					//fmt.Println(table.StartAddr+i*8+j, true)
				} else {
					//fmt.Println(table.StartAddr+i*8+j, false)
					dev.RegBufTable.DiscreteInputs[table.StartAddr+i*8+j] = 0
				}
				if dev.RegBufTable.DiscreteInputs[table.StartAddr+i*8+j] != dev.RegBufStateTable.DiscreteInputs[table.StartAddr+i*8+j] {
					dev.RegBufStateTable.DiscreteInputs[table.StartAddr+i*8+j] = dev.RegBufTable.DiscreteInputs[table.StartAddr+i*8+j]
					dev.RegBufStateTable.LockData.Lock()
					dev.RegBufStateTable.DiscreteState[table.StartAddr+i*8+j] = 1
					dev.RegBufStateTable.Timestamp = time.Now()
					dev.RegBufStateTable.LockData.Unlock()
				}
			}

		}
	}
}

func (dev *DevInfoTable) HoldingRegRead(table Collect_table) {
	for {
		time.Sleep(time.Millisecond * time.Duration(table.Period))
		if dev.DevInfo.InterType == "ETHERNET" {
			dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
		} else if dev.DevInfo.InterType == "RS485" {
			dev.DevInfo.HandleRtuClient.SlaveId = byte(dev.DevInfo.DevId)
		}

		results, err := dev.DevInfo.ModbusClient.ReadHoldingRegisters(uint16(table.StartAddr), uint16(table.Length))
		if err != nil {
			fmt.Println(err, table.StartAddr, table.Length)
		}
		for i := 0; i < len(results); i += 2 {
			u := binary.BigEndian.Uint16(results[i : i+2])
			dev.RegBufTable.HoldingRegisters[table.StartAddr+i/2] = u

			//fmt.Println(u, table.StartAddr+i/2)
			if dev.RegBufTable.HoldingRegisters[table.StartAddr+i/2] != dev.RegBufStateTable.HoldingRegisters[table.StartAddr+i/2] {
				dev.RegBufStateTable.HoldingRegisters[table.StartAddr+i/2] = dev.RegBufTable.HoldingRegisters[table.StartAddr+i/2]
				//fmt.Println(table.StartAddr+i/2, "=", dev.RegBufTable.HoldingRegisters[table.StartAddr+i/2], u, dev.RegBufStateTable.HoldingRegisters[table.StartAddr+i/2])
				dev.RegBufStateTable.LockData.Lock()
				dev.RegBufStateTable.HoldingState[table.StartAddr+i/2] = 1
				dev.RegBufStateTable.Timestamp = time.Now()
				dev.RegBufStateTable.LockData.Unlock()
			}
		}
	}
}

func (dev *DevInfoTable) InputRegRead(table Collect_table) {
	for {
		time.Sleep(time.Millisecond * time.Duration(table.Period))
		if dev.DevInfo.InterType == "ETHERNET" {
			dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
		} else if dev.DevInfo.InterType == "RS485" {
			dev.DevInfo.HandleRtuClient.SlaveId = byte(dev.DevInfo.DevId)
		}
		//dev.DevInfo.HandleClient.SlaveId = byte(dev.DevInfo.DevId)
		//fmt.Println("inputRegRead:", table.StartAddr, table.Length, dev.DevInfo.HandleClient.SlaveId)
		results, err := dev.DevInfo.ModbusClient.ReadInputRegisters(uint16(table.StartAddr), uint16(table.Length))
		if err != nil {
			fmt.Println("InputRegRead:", err, table.StartAddr, table.Length)
			//} else {
			//	fmt.Println("inputreg len:", len(results), table.StartAddr, table.Length)
			//}
		}

		for i := 0; i < len(results); i += 2 {
			u := binary.BigEndian.Uint16(results[i : i+2])
			dev.RegBufTable.InputRegisters[table.StartAddr+i/2] = u
			if dev.RegBufTable.InputRegisters[table.StartAddr+i/2] != dev.RegBufStateTable.InputRegisters[table.StartAddr+i/2] {
				dev.RegBufStateTable.InputRegisters[table.StartAddr+i/2] = dev.RegBufTable.InputRegisters[table.StartAddr+i/2]
				dev.RegBufStateTable.LockData.Lock()
				dev.RegBufStateTable.InputState[table.StartAddr+i/2] = 1
				dev.RegBufStateTable.Timestamp = time.Now()
				dev.RegBufStateTable.LockData.Unlock()
			}
		}
	}
}

func (dev *DevInfoTable) ModbusRegRead() {
	for _, collectTable := range dev.Collect {
		switch collectTable.FunctionCode {
		case 1:
			go dev.CoilRegRead(collectTable)
			break
		case 4:
			go dev.InputRegRead(collectTable)
			break
		case 2:
			go dev.DiscreteInputRegRead(collectTable)
			break
		case 3:
			go dev.HoldingRegRead(collectTable)
			break
		}
	}
}

func (reg *DevRegister) inputReportData(msg *ReportDataMsg, val uint16, regData []uint16) {
	dataVal := DataVal{}
	if reg.BitIndex != -1 {
		dataVal.Key = reg.Key
		val1 := val >> reg.BitIndex & 1
		dataVal.Val = float32(val1)
		//dataVal.TimeStamp = time.Now().Format(time.DateTime)
		dataVal.RegAddr = reg.RegAddr
		dataVal.Name = reg.RegName
		msg.Data = append(msg.Data, dataVal)
	} else if reg.Expre != "" {
		dataVal.Key = reg.Key
		expression := util.Lua_expression(reg.Expre, regData)
		dataVal.Val = expression
		//dataVal.TimeStamp = time.Now().Format(time.DateTime)
		dataVal.Name = reg.RegName
		dataVal.RegAddr = reg.RegAddr
		msg.Data = append(msg.Data, dataVal)
	} else {
		var float float64
		switch reg.DataType {
		case DATA_TYPE_INT16:
			float, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", float32(int16(regData[reg.RegAddr]))*reg.Ratio), 64)
			break
		case DATA_TYPE_UINT16:
			float, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", float32(regData[reg.RegAddr])*reg.Ratio), 64)
			break
		case DATA_TYPE_INT32:
			var int32v int32
			if reg.BigLittle == BIG_ENDIAN {
				int32v = int32(regData[reg.RegAddr]<<16) | int32(regData[reg.RegAddr+1])
			} else {
				int32v = int32(regData[reg.RegAddr+1]<<16) | int32(regData[reg.RegAddr])
			}
			float, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", float32(int32v)*reg.Ratio), 64)
		case DATA_TYPE_UINT32:
			var uint32v uint32
			if reg.BigLittle == BIG_ENDIAN {
				uint32v = uint32(regData[reg.RegAddr])<<16 | uint32(regData[reg.RegAddr+1])
			} else {
				uint32v = uint32(regData[reg.RegAddr+1])<<16 | uint32(regData[reg.RegAddr])
			}
			float, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", float32(uint32v)*reg.Ratio), 64)
		}
		fmt.Println(reg.RegName, reg.Key, float, reg.Unit)
		dataVal.Key = reg.Key
		dataVal.Val = float32(float)
		//dataVal.TimeStamp = time.Now().Format(time.DateTime)
		dataVal.Name = reg.RegName
		dataVal.RegAddr = reg.RegAddr
		msg.Data = append(msg.Data, dataVal)
	}
}

func mqtt_publish_msg(client mqtt.Client, payload []byte) {
	publish := client.Publish("/dev/report/colloc/data", 0, false, payload)
	fmt.Println(publish)

}

func (dev *DevInfoTable) Coil_Show() bool {
	msg := ReportDataMsg{}
	for _, register := range dev.RegTable.CoisReg {
		if dev.RegBufStateTable.CoilsState[register.RegAddr] == 1 {
			fmt.Println(dev.DevInfo.DevName + ":")
			fmt.Println(register.RegName, register.Key, dev.RegBufTable.Coils[register.RegAddr])
			val := DataVal{}
			val.Key = register.Key
			val.Val = float32(dev.RegBufTable.Coils[register.RegAddr])
			//val.TimeStamp = time.Now().Format(time.DateTime)
			val.Name = register.RegName
			val.RegAddr = register.RegAddr
			msg.Data = append(msg.Data, val)
			dev.RegBufStateTable.LockData.Lock()
			dev.RegBufStateTable.CoilsState[register.RegAddr] = 0

			dev.RegBufStateTable.LockData.Unlock()
		}
	}
	if len(msg.Data) > 0 {
		msg.DevName = dev.DevInfo.DevName
		msg.DevType = dev.DevInfo.DevType
		msg.TimeStamp = dev.RegBufStateTable.Timestamp
		msg.ParaType = "yk"
		marshal, _ := json.Marshal(msg)

		if dev.DevInfo.MqttClient != nil {
			fmt.Println(string(marshal))
			dev.RegBufStateTable.LockData.Lock()
			//dev.DevInfo.MqttClient.Publish("/dev/report/colloc/data", 0, false, marshal)
			mqtt_publish_msg(dev.DevInfo.MqttClient, marshal)
			dev.RegBufStateTable.LockData.Unlock()
			return true
		}
	}
	return false
}

func (dev *DevInfoTable) InputReg_show() bool {
	msg := ReportDataMsg{}
	for _, register := range dev.RegTable.InputReg {
		switch register.DataType {
		case DATA_TYPE_INT16, DATA_TYPE_UINT16:
			if dev.RegBufStateTable.InputState[register.RegAddr] == 1 {
				fmt.Println(dev.DevInfo.DevName + ":")
				register.inputReportData(&msg, dev.RegBufTable.InputRegisters[register.RegAddr], dev.RegBufTable.InputRegisters)
				if register.ListTable != nil {
					for _, devRegister := range register.ListTable {
						devRegister.inputReportData(&msg, dev.RegBufTable.InputRegisters[register.RegAddr], dev.RegBufTable.InputRegisters)
					}
				}
				dev.RegBufStateTable.LockData.Lock()
				dev.RegBufStateTable.InputState[register.RegAddr] = 0

				dev.RegBufStateTable.LockData.Unlock()
			}
		case DATA_TYPE_INT32, DATA_TYPE_UINT32:
			if dev.RegBufStateTable.InputState[register.RegAddr] == 1 || dev.RegBufStateTable.InputState[register.RegAddr+1] == 1 {
				fmt.Println(dev.DevInfo.DevName + ":")
				register.inputReportData(&msg, dev.RegBufTable.InputRegisters[register.RegAddr], dev.RegBufTable.InputRegisters)
				if register.ListTable != nil {
					for _, devRegister := range register.ListTable {
						devRegister.inputReportData(&msg, dev.RegBufTable.InputRegisters[register.RegAddr], dev.RegBufTable.InputRegisters)
					}
				}
				dev.RegBufStateTable.LockData.Lock()
				dev.RegBufStateTable.InputState[register.RegAddr] = 0
				dev.RegBufStateTable.InputState[register.RegAddr+1] = 0
				dev.RegBufStateTable.LockData.Unlock()
			}
		}
	}
	if len(msg.Data) > 0 {
		msg.DevName = dev.DevInfo.DevName
		msg.DevType = dev.DevInfo.DevType
		msg.ParaType = "yc"
		msg.TimeStamp = dev.RegBufStateTable.Timestamp
		marshal, _ := json.Marshal(msg)

		if dev.DevInfo.MqttClient != nil {
			fmt.Println(string(marshal))
			dev.RegBufStateTable.LockData.Lock()
			//dev.DevInfo.MqttClient.Publish("/dev/report/colloc/data", 0, false, marshal)
			mqtt_publish_msg(dev.DevInfo.MqttClient, marshal)
			dev.RegBufStateTable.LockData.Unlock()
			return true
		}
	}
	return false
}

func (dev *DevInfoTable) DiscreteInputs_show() bool {
	msg := ReportDataMsg{}
	for _, register := range dev.RegTable.DiscretInput {
		if dev.RegBufStateTable.DiscreteState[register.RegAddr] == 1 {

			fmt.Println(dev.DevInfo.DevName + ":")
			fmt.Println(register.RegName, register.Key, dev.RegBufTable.DiscreteInputs[register.RegAddr])
			val := DataVal{}
			val.Key = register.Key
			val.Val = float32(dev.RegBufTable.DiscreteInputs[register.RegAddr])
			//val.TimeStamp = time.Now().Format(time.DateTime)
			val.Name = register.RegName
			val.RegAddr = register.RegAddr
			msg.Data = append(msg.Data, val)
			dev.RegBufStateTable.LockData.Lock()
			dev.RegBufStateTable.DiscreteState[register.RegAddr] = 0

			dev.RegBufStateTable.LockData.Unlock()
		}
	}
	if len(msg.Data) > 0 {
		msg.DevName = dev.DevInfo.DevName
		msg.DevType = dev.DevInfo.DevType
		msg.TimeStamp = dev.RegBufStateTable.Timestamp
		msg.ParaType = "yx"
		marshal, _ := json.Marshal(msg)

		if dev.DevInfo.MqttClient != nil {
			fmt.Println(string(marshal))
			dev.RegBufStateTable.LockData.Lock()
			//dev.DevInfo.MqttClient.Publish("/dev/report/colloc/data", 0, false, marshal)
			mqtt_publish_msg(dev.DevInfo.MqttClient, marshal)
			dev.RegBufStateTable.LockData.Unlock()
			return true
		}
	}
	return false
}

func (dev *DevInfoTable) HoldingReg_show() bool {
	msg := ReportDataMsg{}
	for _, register := range dev.RegTable.HoldingReg {
		switch register.DataType {
		case DATA_TYPE_INT16, DATA_TYPE_UINT16:
			if dev.RegBufStateTable.HoldingState[register.RegAddr] == 1 {

				register.inputReportData(&msg, dev.RegBufTable.HoldingRegisters[register.RegAddr], dev.RegBufTable.HoldingRegisters)
				if register.ListTable != nil {
					for _, devRegister := range register.ListTable {
						devRegister.inputReportData(&msg, dev.RegBufTable.HoldingRegisters[register.RegAddr], dev.RegBufTable.HoldingRegisters)
					}
				}
				dev.RegBufStateTable.LockData.Lock()
				dev.RegBufStateTable.HoldingState[register.RegAddr] = 0
				dev.RegBufStateTable.LockData.Unlock()
			}
		case DATA_TYPE_INT32, DATA_TYPE_UINT32:
			if dev.RegBufStateTable.HoldingState[register.RegAddr] == 1 || dev.RegBufStateTable.HoldingState[register.RegAddr+1] == 1 {
				register.inputReportData(&msg, dev.RegBufTable.HoldingRegisters[register.RegAddr], dev.RegBufTable.HoldingRegisters)
				if register.ListTable != nil {
					for _, devRegister := range register.ListTable {
						devRegister.inputReportData(&msg, dev.RegBufTable.HoldingRegisters[register.RegAddr], dev.RegBufTable.HoldingRegisters)
					}
				}
				dev.RegBufStateTable.LockData.Lock()
				dev.RegBufStateTable.HoldingState[register.RegAddr] = 0
				dev.RegBufStateTable.HoldingState[register.RegAddr+1] = 0
				dev.RegBufStateTable.LockData.Unlock()
			}
		}

	}
	if len(msg.Data) > 0 {
		msg.DevName = dev.DevInfo.DevName
		msg.DevType = dev.DevInfo.DevType
		msg.TimeStamp = dev.RegBufStateTable.Timestamp
		msg.ParaType = "yt"
		marshal, _ := json.Marshal(msg)

		if dev.DevInfo.MqttClient != nil {
			fmt.Println(string(marshal))
			//dev.DevInfo.MqttClient.Publish("/dev/report/colloc/data", 0, false, marshal)
			dev.RegBufStateTable.LockData.Lock()
			mqtt_publish_msg(dev.DevInfo.MqttClient, marshal)
			dev.RegBufStateTable.LockData.Unlock()
			return true
		}
	}
	return false
}

var (
	MQTT_CONNECT = 0
)

func Mqtt_connectSet(conflag int) {
	MQTT_CONNECT = conflag
}

func Collect_Show(dev *DevInfoTable) {
	if MQTT_CONNECT == 0 {
		return
	}
	show := dev.Coil_Show()
	if show == true {
		return
	}
	inputsShow := dev.DiscreteInputs_show()
	if inputsShow == true {
		return
	}
	regShow := dev.InputReg_show()
	if regShow == true {
		return
	}
	reg_show := dev.HoldingReg_show()
	if reg_show == true {
		return
	}

}

func (dev *DevInfoTable) CoilRegWrite(addr uint16, val uint16) {
	dev.DevInfo.ModbusClient.WriteSingleCoil(addr, val)
}

func (dev *DevInfoTable) HoldingRegWrite(addr uint16, val uint16) {
	dev.DevInfo.ModbusClient.WriteSingleRegister(addr, val)
}
